package com.kds.smarthome.service.system;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import com.kds.smarthome.entity.StatisticsItem;
import com.kds.smarthome.mapper.FunctionMapper;
import com.kds.smarthome.mapper.StatisticsItemMapper;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import lombok.extern.slf4j.Slf4j;
import org.bson.BsonNull;
import org.bson.Document;
import org.joda.time.DateTime;
import org.mvel2.MVEL;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author 孔德嵩
 * 创建日期 2025/4/18
 */
@Service
@Slf4j
public class ScheduleEnergyStatistics implements InitializingBean {

    private static final String VARIABLE_PREFIX = "v_";

    @Autowired
    private StatisticsItemMapper statisticsItemMapper;

    private List<StatisticsItem> statisticsItems;

    @Autowired
    private FunctionMapper functionMapper;

    @Autowired
    private MongoClient mongoClient;

    private List<String> fids;

    // 新增缓存属性
    private Set<String> allPossibleVars;
    private Map<String, Map<String, String>> preprocessedExpressions;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.statisticsItems = statisticsItemMapper.selectStatisticsItems(null);
        this.fids = functionMapper.energyFids(null);

        // 初始化所有可能的变量集合
        this.allPossibleVars = initializeAllPossibleVars(statisticsItems);

        // 初始化预处理表达式
        this.preprocessedExpressions = initializePreprocessedExpressions(statisticsItems);

        log.info("能耗统计初始化完成 - 统计项数量: {}, 设备数量: {}, 变量数量: {}",
                statisticsItems.size(), fids.size(), allPossibleVars.size());
    }

    /**
     * 初始化所有可能的变量集合
     */
    private HashSet initializeAllPossibleVars(List<StatisticsItem> statisticsItems) {
        HashSet allPossibleVars = new HashSet<>();
        java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\$\\{([^}]+)}");

        for (StatisticsItem item : statisticsItems) {
            Map<String, String> expressions = item.getItemMap();
            for (String expression : expressions.values()) {
                java.util.regex.Matcher matcher = pattern.matcher(expression);
                while (matcher.find()) {
                    allPossibleVars.add(matcher.group(1));
                }
            }
        }
        return allPossibleVars;
    }

    /**
     * 初始化预处理表达式
     */
    private HashMap initializePreprocessedExpressions(List<StatisticsItem> statisticsItems) {
        HashMap preprocessedExpressions = new HashMap<>();

        for (StatisticsItem item : statisticsItems) {
            Map<String, String> itemExpressions = new HashMap<>();
            Map<String, String> originalExpressions = item.getItemMap();

            for (Map.Entry<String, String> entry : originalExpressions.entrySet()) {
                String itemCode = entry.getKey();
                String expression = entry.getValue();
                // 预处理表达式，将${var}转换为v_var形式
                String mvelExpression = expression.replaceAll("\\$\\{([^}]+)}", VARIABLE_PREFIX + "$1");
                itemExpressions.put(itemCode, mvelExpression);
            }

            preprocessedExpressions.put(item.getBuildingCode(), itemExpressions);
        }

        return preprocessedExpressions;
    }

    // 获取 smarthome 数据库
    public MongoDatabase getSmartHomeDatabase() {
        return mongoClient.getDatabase("smarthome");
    }


    public void resetBuilding(String buildingCode) {

        this.statisticsItems = statisticsItemMapper.selectStatisticsItems(buildingCode);
        this.fids = functionMapper.energyFids(buildingCode);

        // 初始化所有可能的变量集合
        this.allPossibleVars = initializeAllPossibleVars(statisticsItems);

        // 初始化预处理表达式
        this.preprocessedExpressions = initializePreprocessedExpressions(statisticsItems);
    }


    /**
     * 仅算1个小时、1天、1月的
     *
     * @param start
     * @param end
     */
    public void hourStatistics(Date start, Date end) {
        try {
            // 1. 首先执行聚合查询并收集结果
            List<Document> aggregateResults = new ArrayList<>();
            AggregateIterable<Document> result = getSmartHomeDatabase().getCollection("data").aggregate(Arrays.asList(
                    new Document("$match",
                            new Document("ts",
                                    new Document("$gte", start)
                                            .append("$lt", end))
                                    .append("fid", new Document("$in", fids))),
                    new Document("$sort",
                            new Document("ts", 1L)),
                    new Document("$group",
                            new Document("_id", "$fid")
                                    .append("firstRecord",
                                            new Document("$first", "$$ROOT"))
                                    .append("lastRecord",
                                            new Document("$last", "$$ROOT"))),
                    new Document("$addFields",
                            new Document("firstRecord.value",
                                    new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$firstRecord.value"), 0L)))
                                    .append("lastRecord.value",
                                            new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$lastRecord.value"), 0L)))),
                    new Document("$addFields",
                            new Document("deltaMillis",
                                    new Document("$subtract", Arrays.asList("$lastRecord.ts", "$firstRecord.ts")))
                                    .append("deltaMinutes",
                                            new Document("$cond", Arrays.asList(new Document("$eq", Arrays.asList(new Document("$subtract", Arrays.asList("$lastRecord.ts", "$firstRecord.ts")), 0L)), 0L,
                                                    new Document("$divide", Arrays.asList(new Document("$subtract", Arrays.asList("$lastRecord.ts", "$firstRecord.ts")), 1000L * 60L)))))
                                    .append("deltaValue",
                                            new Document("$subtract", Arrays.asList("$lastRecord.value", "$firstRecord.value")))),
                    new Document("$addFields",
                            new Document("adjustedValue",
                                    new Document("$cond", Arrays.asList(new Document("$or", Arrays.asList(new Document("$eq", Arrays.asList("$deltaMinutes", 0L)),
                                                    new Document("$eq", Arrays.asList("$deltaValue",
                                                            new BsonNull())))), 0L,
                                            new Document("$round", Arrays.asList(new Document("$multiply", Arrays.asList(new Document("$divide", Arrays.asList("$deltaValue", "$deltaMinutes")), 60L)), 2L)))))),
                    new Document("$addFields",
                            new Document("tsTruncated",
                                    new Document("$dateFromParts",
                                            new Document("year",
                                                    new Document("$year",
                                                            new Document("date", "$firstRecord.ts")
                                                                    .append("timezone", "+08:00")))
                                                    .append("month",
                                                            new Document("$month",
                                                                    new Document("date", "$firstRecord.ts")
                                                                            .append("timezone", "+08:00")))
                                                    .append("day",
                                                            new Document("$dayOfMonth",
                                                                    new Document("date", "$firstRecord.ts")
                                                                            .append("timezone", "+08:00")))
                                                    .append("hour",
                                                            new Document("$hour",
                                                                    new Document("date", "$firstRecord.ts")
                                                                            .append("timezone", "+08:00")))
                                                    .append("timezone", "+08:00")))),
                    new Document("$project",
                            new Document("_id", 0L)
                                    .append("fid", "$_id")
                                    .append("value", "$adjustedValue")
                                    .append("ts", "$tsTruncated"))
            ));

            // 2. 收集聚合结果
            result.forEach(doc -> aggregateResults.add(doc));

            // 3. 保存到 meter_hour 集合
            MongoCollection<Document> meterHourCollection = getSmartHomeDatabase().getCollection("meter_hour");
            for (Document doc : aggregateResults) {
                Document filter = new Document()
                        .append("fid", doc.getString("fid"))
                        .append("ts", doc.getDate("ts"));

                meterHourCollection.updateOne(
                        filter,
                        new Document("$set", doc),
                        new com.mongodb.client.model.UpdateOptions().upsert(true)
                );
            }

            // 4. 收集计算结果到 valueMap
            Map<String, Object> valueMap = new HashMap<>();
            int processedCount = 0;
            for (Document doc : aggregateResults) {
                processedCount++;
                String fid = doc.getString("fid");
                // 安全地获取数值，处理可能的 Long 或 Double 类型
                Object rawValue = doc.get("value");
                Double value = null;
                if (rawValue != null) {
                    if (rawValue instanceof Number) {
                        value = ((Number) rawValue).doubleValue();
                    } else {
                        value = Convert.toDouble(rawValue, 0.0);
                    }
                } else {
                    value = 0.0;
                }
                valueMap.put(fid, value);
            }

            // 5. 处理统计数据并保存
            processAndSaveStatistics(valueMap, start, "building_hour", "小时");

            log.info("小时能耗统计完成 - 开始时间: {}, 结束时间: {}, 处理设备数: {}, 处理记录数: {}",
                    start, end, fids.size(), processedCount);

        } catch (Exception e) {
            log.error("小时能耗统计失败 - 开始时间: {}, 结束时间: {}, 设备数: {}, 错误: {}",
                    start, end, fids.size(), e.getMessage(), e);
            throw new RuntimeException("小时能耗统计失败", e);
        }
    }

    public void dayStatistics(Date start, Date end) {
        try {
            MongoCollection<Document> collection = getSmartHomeDatabase().getCollection("data");

            // 1. 首先执行聚合查询并收集结果
            List<Document> aggregateResults = new ArrayList<>();
            AggregateIterable<Document> result = collection.aggregate(Arrays.asList(
                    new Document("$match",
                            new Document("ts",
                                    new Document("$gte", start)
                                            .append("$lt", end))
                                    .append("fid", new Document("$in", fids))),
                    new Document("$sort",
                            new Document("ts", 1L)),
                    new Document("$group",
                            new Document("_id", "$fid")
                                    .append("firstRecord",
                                            new Document("$first", "$$ROOT"))
                                    .append("lastRecord",
                                            new Document("$last", "$$ROOT"))),
                    new Document("$addFields",
                            new Document("firstRecord.value",
                                    new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$firstRecord.value"), 0L)))
                                    .append("lastRecord.value",
                                            new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$lastRecord.value"), 0L)))),
                    new Document("$addFields",
                            new Document("deltaValue",
                                    new Document("$subtract", Arrays.asList("$lastRecord.value", "$firstRecord.value")))
                                    .append("tsTruncated",
                                            new Document("$dateFromParts",
                                                    new Document("year",
                                                            new Document("$year",
                                                                    new Document("date", "$firstRecord.ts")
                                                                            .append("timezone", "+08:00")))
                                                            .append("month",
                                                                    new Document("$month",
                                                                            new Document("date", "$firstRecord.ts")
                                                                                    .append("timezone", "+08:00")))
                                                            .append("day",
                                                                    new Document("$dayOfMonth",
                                                                            new Document("date", "$firstRecord.ts")
                                                                                    .append("timezone", "+08:00")))
                                                            .append("timezone", "+08:00")))),
                    new Document("$project",
                            new Document("_id", 0L)
                                    .append("fid", "$_id")
                                    .append("value",
                                            new Document("$round", Arrays.asList("$deltaValue", 1L)))
                                    .append("ts", "$tsTruncated"))
            ));

            // 2. 收集聚合结果
            result.forEach(doc -> aggregateResults.add(doc));

            // 3. 保存到 meter_day 集合
            MongoCollection<Document> meterDayCollection = getSmartHomeDatabase().getCollection("meter_day");
            for (Document doc : aggregateResults) {
                Document filter = new Document()
                        .append("fid", doc.getString("fid"))
                        .append("ts", doc.getDate("ts"));

                meterDayCollection.updateOne(
                        filter,
                        new Document("$set", doc),
                        new com.mongodb.client.model.UpdateOptions().upsert(true)
                );
            }

            // 4. 收集计算结果到 valueMap
            Map<String, Object> valueMap = new HashMap<>();
            int processedCount = 0;
            for (Document doc : aggregateResults) {
                processedCount++;
                String fid = doc.getString("fid");
                // 安全地获取数值，处理可能的 Long 或 Double 类型
                Object rawValue = doc.get("value");
                Double value = null;
                if (rawValue != null) {
                    if (rawValue instanceof Number) {
                        value = ((Number) rawValue).doubleValue();
                    } else {
                        value = Convert.toDouble(rawValue, 0.0);
                    }
                } else {
                    value = 0.0;
                }
                valueMap.put(fid, value);
            }

            // 5. 处理统计数据并保存
            processAndSaveStatistics(valueMap, start, "building_day", "日");

            log.info("日能耗统计完成 - 开始时间: {}, 结束时间: {}, 处理设备数: {}, 处理记录数: {}",
                    start, end, fids.size(), processedCount);

        } catch (Exception e) {
            log.error("日能耗统计失败 - 开始时间: {}, 结束时间: {}, 设备数: {}, 错误: {}",
                    start, end, fids.size(), e.getMessage(), e);
            throw new RuntimeException("日能耗统计失败", e);
        }
    }

    public void monthStatistics(Date start, Date end) {
        try {
            // 1. 首先执行聚合查询并收集结果
            List<Document> aggregateResults = new ArrayList<>();
            AggregateIterable<Document> result = getSmartHomeDatabase().getCollection("data").aggregate(Arrays.asList(
                    new Document("$match",
                            new Document("ts",
                                    new Document("$gte", start)
                                            .append("$lt", end))
                                    .append("fid", new Document("$in", fids))),
                    new Document("$sort",
                            new Document("ts", 1L)),
                    new Document("$group",
                            new Document("_id", "$fid")
                                    .append("firstRecord",
                                            new Document("$first", "$$ROOT"))
                                    .append("lastRecord",
                                            new Document("$last", "$$ROOT"))),
                    new Document("$addFields",
                            new Document("firstRecord.value",
                                    new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$firstRecord.value"), 0L)))
                                    .append("lastRecord.value",
                                            new Document("$ifNull", Arrays.asList(new Document("$toDouble", "$lastRecord.value"), 0L)))),
                    new Document("$addFields",
                            new Document("deltaValue",
                                    new Document("$subtract", Arrays.asList("$lastRecord.value", "$firstRecord.value")))
                                    .append("tsTruncated",
                                            new Document("$dateFromParts",
                                                    new Document("year",
                                                            new Document("$year",
                                                                    new Document("date", "$firstRecord.ts")
                                                                            .append("timezone", "+08:00")))
                                                            .append("month",
                                                                    new Document("$month",
                                                                            new Document("date", "$firstRecord.ts")
                                                                                    .append("timezone", "+08:00")))
                                                            .append("timezone", "+08:00")))),
                    new Document("$project",
                            new Document("_id", 0L)
                                    .append("fid", "$_id")
                                    .append("value",
                                            new Document("$round", Arrays.asList("$deltaValue", 1L)))
                                    .append("ts", "$tsTruncated"))
            ));

            // 2. 收集聚合结果
            result.forEach(doc -> aggregateResults.add(doc));

            // 3. 保存到 meter_month 集合
            MongoCollection<Document> meterMonthCollection = getSmartHomeDatabase().getCollection("meter_month");
            for (Document doc : aggregateResults) {
                Document filter = new Document()
                        .append("fid", doc.getString("fid"))
                        .append("ts", doc.getDate("ts"));

                meterMonthCollection.updateOne(
                        filter,
                        new Document("$set", doc),
                        new com.mongodb.client.model.UpdateOptions().upsert(true)
                );
            }

            // 4. 收集计算结果到 valueMap
            Map<String, Object> valueMap = new HashMap<>();
            int processedCount = 0;
            for (Document doc : aggregateResults) {
                processedCount++;
                String fid = doc.getString("fid");
                // 安全地获取数值，处理可能的 Long 或 Double 类型
                Object rawValue = doc.get("value");
                Double value = null;
                if (rawValue != null) {
                    if (rawValue instanceof Number) {
                        value = ((Number) rawValue).doubleValue();
                    } else {
                        value = Convert.toDouble(rawValue, 0.0);
                    }
                } else {
                    value = 0.0;
                }
                valueMap.put(fid, value);
            }

            // 5. 处理统计数据并保存
            processAndSaveStatistics(valueMap, start, "building_month", "月");

            log.info("月能耗统计完成 - 开始时间: {}, 结束时间: {}, 处理设备数: {}, 处理记录数: {}",
                    start, end, fids.size(), processedCount);

        } catch (Exception e) {
            log.error("月能耗统计失败 - 开始时间: {}, 结束时间: {}, 设备数: {}, 错误: {}",
                    start, end, fids.size(), e.getMessage(), e);
            throw new RuntimeException("月能耗统计失败", e);
        }
    }

    /**
     * 处理统计数据并保存到MongoDB
     *
     * @param valueMap       设备值映射
     * @param timestamp      统计时间点
     * @param collectionName 目标集合名称
     * @param statisticsType 统计类型（用于日志）
     */
    private void processAndSaveStatistics(Map<String, Object> valueMap, Date timestamp,
                                          String collectionName, String statisticsType) {
        List<Document> documents = new ArrayList<>();

        for (StatisticsItem item : statisticsItems) {
            Document doc = new Document();
            doc.put("building_code", item.getBuildingCode());
            doc.put("ts", timestamp);

            Map<String, String> expressions = preprocessedExpressions.get(item.getBuildingCode());
            if (expressions == null) {
                continue;
            }

            for (Map.Entry<String, String> entry : expressions.entrySet()) {
                String itemCode = entry.getKey();
                String mvelExpression = entry.getValue();

                try {
                    // 准备变量映射
                    Map<String, Object> vars = new HashMap<>();

                    // 为所有可能的变量设置默认值 0.0
                    for (String varName : allPossibleVars) {
                        vars.put(VARIABLE_PREFIX + varName, 0.0);
                    }

                    // 将实际的变量值覆盖默认值
                    for (Map.Entry<String, Object> valueEntry : valueMap.entrySet()) {
                        String varName = VARIABLE_PREFIX + valueEntry.getKey();
                        Object varValue = valueEntry.getValue();
                        vars.put(varName, varValue != null ? varValue : 0.0);
                    }

                    // 使用MVEL计算表达式
                    Object result = MVEL.eval(mvelExpression, vars);
                    if (result instanceof Number) {
                        // 将结果转换为 double 并保留一位小数
                        double roundedValue = NumberUtil.round(((Number) result).doubleValue(), 1).doubleValue();
                        doc.put(itemCode, roundedValue);
                    }
                } catch (Exception e) {
                    log.error("计算{}能耗统计表达式失败 - 建筑: {}, 项目: {}, 表达式: {}, 错误: {}",
                            statisticsType, item.getBuildingCode(), itemCode, mvelExpression, e.getMessage());
                }
            }

            documents.add(doc);
        }

        if (!documents.isEmpty()) {
            try {
                MongoCollection<Document> collection = getSmartHomeDatabase().getCollection(collectionName);
                for (Document doc : documents) {
                    Document filter = new Document()
                            .append("building_code", doc.getString("building_code"))
                            .append("ts", doc.getDate("ts"));

                    collection.updateOne(
                            filter,
                            new Document("$set", doc),
                            new com.mongodb.client.model.UpdateOptions().upsert(true)
                    );
                }
                log.info("成功保存{}条{}能耗统计数据到MongoDB集合{}", documents.size(), statisticsType, collectionName);
            } catch (Exception e) {
                log.error("保存{}能耗统计数据到MongoDB失败: {}", statisticsType, e.getMessage(), e);
                throw new RuntimeException("保存" + statisticsType + "能耗统计数据失败", e);
            }
        }
    }


    public void hourStatistics() {

        DateTime now = new DateTime();
        DateTime end;
        DateTime start;

        end = now.hourOfDay().roundFloorCopy();
        start = end.minusHours(1);

        this.hourStatistics(start.toDate(), end.toDate());
    }

    public void dayStatistics() {
        DateTime now = new DateTime();
        DateTime end;
        DateTime start;

        end = now.dayOfMonth().roundFloorCopy();
        start = end.minusDays(1);
        this.dayStatistics(start.toDate(), end.toDate());
    }


    public void monthStatistics() {

        DateTime now = new DateTime();
        DateTime end;
        DateTime start;

        end = now.dayOfMonth().roundFloorCopy().withDayOfMonth(1);
        start = end.minusMonths(1);
        this.monthStatistics(start.toDate(), end.toDate());
    }
}
